package com.test.config.shiro.realms;

import com.auth0.jwt.exceptions.AlgorithmMismatchException;
import com.auth0.jwt.exceptions.InvalidClaimException;
import com.auth0.jwt.exceptions.SignatureVerificationException;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.test.config.shiro.jwtConfig.JwtToken;
import com.test.entity.Permission;
import com.test.entity.User;
import com.test.service.UserService;
import com.test.util.ApplicationContextUtils;
import com.test.util.jwtUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;

import java.util.List;

import static com.test.controller.LoginController.JWT_TOKEN;

/**
 * 自定义的realm
 */
@Slf4j
public class MyCustomerRealm extends AuthorizingRealm {

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JwtToken;
    }

    @Autowired
    private RedisTemplate redisTemplate;
    // @Autowired  private UserService userService;
    //此处使用一个自定义的获取spring中所有bean的工具类,获取到查询用户信息的接口bean

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        System.out.println("+++++++++++++++++++++进入自定的realm进行授权操作++++++++++++++++++++++++");

        log.info("授权的原始参数 :{}", principalCollection);
        String primaryPrincipal = (String) principalCollection.getPrimaryPrincipal();

        //调用获取所有bean的工具类,根据用户名查询当前用户的所有的角色信息,可以直接注入bean
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
        User user_db = userService.findRolesByUserName(primaryPrincipal);

        //log.info("用户授权得到的角色信息 : {}", user.getRoles());
        log.info("根据名称查询数据库的用户角色信息 : {}", user_db.getRoles());

        if (user_db.getRoles() != null) {
            //授权具体执行的类 ,将查询到的用户的角色信息,添加到shiro中
            SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
            user_db.getRoles().forEach(role -> {
                simpleAuthorizationInfo.addRole(role.getRoleName());    //用户下多个角色信息遍历,交给shiro

                //根据认证的角色id,查询角色信息对应的多个权限信息
                List<Permission> perms = userService.findPermsByRoleId(role.getRId());
                perms.forEach(perm -> {
                    simpleAuthorizationInfo.addStringPermission(perm.getPermissionName()); //查询用户下每个角色对应的权限信息,交给shiro
                });
            });  //此处的角色信息应该是从数据库查询到的,也就是当前用户拥有的角色和权限  验证权限三种方式(1页面控制 2代码判断 3注解@RequestRole)
            //拥有/user下的 添加,查询,修改操作,无删除权限不显示
            return simpleAuthorizationInfo;  //将角色信息方会给shiro
        }
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        System.out.println("-------------------进入自定的realm进行认证操作-------------------------");
        String token = (String) authenticationToken.getPrincipal();
        log.info("用户的token信息 : {}", token);

        //传递过来的是jwt加密过的token,进行解析,得到用户名
        DecodedJWT decodedJWT = jwtUtil.verifyToken(token);
        String userName = String.valueOf(decodedJWT.getClaim("userName").asString());

        log.info("jwt解析得到用户名称 : {}", userName);

        //根据用户名.查询数据库中用户信息, 此处使用一个自定义的获取spring中所有bean的工具类,获取到查询用户信息的接口bean ----  ser user = userService.findUserByNmae(principal);
        UserService userService = (UserService) ApplicationContextUtils.getBean("userService");
        System.out.println("获取到的bean :" + userService);
        User user = userService.findUserByNmae(userName);
        log.info("根据用户名查询数据库用户信息 : [{}]", user.toString());
        if (user == null) {
            throw new UnknownAccountException("账户不存在");
        }

        //获取redis中对应用户的token信息
        String redisUserToken = (String) redisTemplate.opsForValue().get(JWT_TOKEN + userName);
        if (redisUserToken == null) {
            throw new RuntimeException("登陆过期，请重新登陆");
        }

        try {
            //redis中获取的用户的token,进行解析,没异常说明正确,有异常抛出对应异常
            DecodedJWT decodedJWT1 = jwtUtil.verifyToken(redisUserToken);
            log.info("redis中的token解析正确");
            return new SimpleAuthenticationInfo(user.getUsername(), authenticationToken.getCredentials().toString(), this.getName());
        } catch (SignatureVerificationException e) {
            e.printStackTrace();
            throw new RuntimeException("签名不一致异常");
        } catch (TokenExpiredException e) {
            e.printStackTrace();
            throw new RuntimeException("令牌过期异常");
        } catch (AlgorithmMismatchException e) {
            e.printStackTrace();
            throw new RuntimeException("算法不匹配异常");
        } catch (InvalidClaimException e) {
            e.printStackTrace();
            throw new RuntimeException("失效的payload异常");
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("token无效");
        }
    }
}
